#include "Storage.h"
#include <QtGui/private/qzipwriter_p.h>

Storage::Storage()
{
    QSettings settings("./config.ini", QSettings::IniFormat);
    oss_key_ = settings.value("oss_key").toString().toStdString();
    oss_secret_ = settings.value("oss_secret").toString().toStdString();
    oss_endpoint_ = settings.value("oss_endpoint").toString().toStdString();
    oss_bucket_ = settings.value("oss_bucket").toString().toStdString();

    zipStoragePath_ = settings.value("zip_path").toString();

    this->start();
    this->moveToThread(this);
    QTimer::singleShot(0, this, &Storage::onInit);
}

Storage::~Storage()
{

}

void Storage::setStorage(int type)
{
    type_ = type;
}

void Storage::setRootPath(const QString &filePath)
{
    QFileInfo fileInfo(filePath);
    gitRootPath_ = fileInfo.absolutePath();
    gitRootFilePath_ = fileInfo.absoluteFilePath();
    qDebug() << "set root path:" << filePath;
}

void Storage::watchPath(const QString &filePath)
{
    if (type_ == 0)
        return;

    //qDebug() << "request:" << filePath;
    QFileInfo fileInfo(gitRootPath_ + "/" + filePath);
    QString fileDestPath = fileInfo.absolutePath();
    //qDebug() << "path:" << fileDestPath;

    QFileInfoList dirs = getAllDirs(fileDestPath);
    dirs << QFileInfo(fileDestPath);

    QMetaObject::invokeMethod(this, "onWatchPaths", Qt::BlockingQueuedConnection,
                              Q_ARG(QFileInfoList, dirs));
}

QFileInfoList Storage::getAllFiles(const QString &filePath) {
    QFileInfoList ret;
    QDir dir(filePath);
    QFileInfoList files = dir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
    for (const QFileInfo &f : files) {
        if (f.isDir()) {
            ret << getAllFiles(f.absoluteFilePath());
        } else {
            ret << f;
        }
    }
    return ret;
}

QFileInfoList Storage::getFiles(const QString &filePath)
{
    return QDir(filePath).entryInfoList(QDir::Files | QDir::NoDotAndDotDot);
}

QFileInfoList Storage::getAllDirs(const QString &filePath)
{
    QFileInfoList ret;
    QDir dir(filePath);
    QFileInfoList files = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
    for (const QFileInfo &f : files) {
        ret << f;
        if (f.isDir()) {
            ret << getAllDirs(f.absoluteFilePath());
        }
    }
    return ret;
}

QString Storage::writeToZipFile(const QFileInfoList &files)
{
    auto getIndex = [](const QString &filePath) ->int {
        int index = filePath.indexOf("refs");
        if (index == -1) {
            index = filePath.indexOf("objects");
            if (index == -1) {
                index = filePath.indexOf("info");
                if (index == -1) {
                    index = filePath.indexOf("hooks");
                }
            }
        }
        return index;
    };

    int index = -1;
    QString fileDest;
    for (const QFileInfo &file : files) {
        QString filePath = file.absoluteFilePath();
        index = getIndex(filePath);
        if (index != -1) {
            fileDest = filePath;
            break;
        }
    }
    if (index == -1) {
        //qDebug() << "git project dir not found:" << files;
        return QString();
    }

    fileDest = fileDest.mid(0, index);
    QFileInfo fileInfo(QFileInfo(fileDest).canonicalFilePath()); //格式化
    fileDest = fileInfo.absoluteFilePath() + "/";

    //组合出zip的目录
    QString dirName = fileInfo.fileName();
    if (zipStoragePath_.isEmpty()) {
        fileInfo = QFileInfo(fileDest + dirName + ".zip");
    } else {
        fileInfo = QFileInfo(zipStoragePath_ + "/" + dirName + ".zip");
    }

    QString zipFilePath = fileInfo.absoluteFilePath();

    //不存在, 就把目录下面所有目录加进来

    QZipWriter zipwriter(zipFilePath);
    QFileInfoList allFiles;

    //red 暂时没有办法对zip文件进读写, 只能全部压缩
    //    if (fileInfo.exists()) {
    //        allFiles = files;
    //    } else {
    allFiles = this->getAllFiles(fileDest);
    allFiles.removeAll(QFileInfo(zipFilePath));
    //    }

    for (const QFileInfo &f : allFiles) {
        QString fullPath = f.absoluteFilePath();

        //zip里面的相对路径
        QString absPath = fullPath;
        absPath.remove(fileDest);

        QFile qfile(f.absoluteFilePath());
        if (!qfile.open(QIODevice::ReadOnly)) {
            qWarning() << "open file failed:" << f.absoluteFilePath();
            continue;
        }
        QByteArray content = qfile.readAll();
        qfile.close();

        zipwriter.addFile(absPath, content);
    }
    zipwriter.close();

    return zipFilePath;
}

bool Storage::saveToOssServer(const QFileInfoList &files)
{
    return true;
}

void Storage::updateFiles()
{
    QMetaObject::invokeMethod(this, "onUpdateFiles", Qt::BlockingQueuedConnection);
}

void Storage::onInit()
{
    watcher_ = QSharedPointer<QFileSystemWatcher>(new QFileSystemWatcher());
    connect(watcher_.data(), &QFileSystemWatcher::directoryChanged,
            this, &Storage::onDirectoryChanged);
    connect(watcher_.data(), &QFileSystemWatcher::fileChanged,
            this, &Storage::onFileChanged);
}

void Storage::onWatchPaths(const QFileInfoList &dirs)
{
    QStringList lastDirs = watcher_->directories();
    if (!lastDirs.isEmpty()) {
        watcher_->removePaths(lastDirs);
        watches_.clear();
    }

    for (const QFileInfo &f : dirs) {
        QString watchPath = f.absoluteFilePath();
        //qDebug() << "add watch:" << watchPath;
        watcher_->addPath(watchPath);
        watches_.insert(watchPath);

        //存下所有文件的修改时间
        QFileInfoList files = this->getFiles(watchPath);
        for (const QFileInfo &file : files) {
            lastModified_.insert(file.absoluteFilePath(), file.lastModified());
        }
    }

}

void Storage::onUpdateFiles()
{
    if (type_ == 0)
        return;

    QStringList dirs = watcher_->directories();
    if (!dirs.isEmpty()) {
        //qDebug() << "remove watches:" << dirs;
        watcher_->removePaths(dirs);
    }

    QFileInfoList finalFiles;
    //qDebug() << "final changes:" << changes_;
    for (auto itr = changes_.begin(); itr != changes_.end(); ++itr) {
        QFileInfo fileInfo(*itr);
        if (!fileInfo.exists())
            continue;
        if (fileInfo.isDir()) {
            finalFiles << this->getAllFiles(fileInfo.absoluteFilePath());
        } else {
            finalFiles << fileInfo;
        }
    }

    if (!finalFiles.isEmpty()) {

        switch(type_) {
        case 1: {
            QString zipFile = writeToZipFile(finalFiles);
            if (zipFile.isEmpty()) {
                qWarning() << "write zip files error.";
            }
            break;
        }
        case 2: {

            if (!saveToOssServer(finalFiles)) {
                qWarning() << "upload to oss failed.";
            }
            break;
        }
        }

    }

    watches_.clear();
    lastModified_.clear();
    changes_.clear();
}

void Storage::onFileChanged(const QString &path)
{
    //qDebug() << "=============== file changed:" << path;
}

void Storage::onDirectoryChanged(const QString &path)
{
    //qDebug() << "=============== dir changed:" << path;
    //获取目录, 看看是否新增加了目录, 如果是,就保存到发生变化的容器里.
    QFileInfoList dirs = this->getAllDirs(path);
    for (const QFileInfo &f : dirs) {
        QString dest = f.absoluteFilePath();
        if (watches_.contains(dest))
            continue;
        //记录下来
        changes_.insert(dest);
    }

    QFileInfoList files = this->getFiles(path);
    for (const QFileInfo &file : files) {
        QString filePath = file.absoluteFilePath();
        auto itr = lastModified_.find(filePath);
        if (itr == lastModified_.end()) {
            //新增加的文件
            changes_.insert(filePath);
            continue;
        }
        //发生了变化
        if (itr.value() != file.lastModified()) {
            changes_.insert(filePath);
        }
    }
}
